
#include "Calendar.h"
#include <cmath>

const char Calendar::monthLength[14] = { 31,28,31,30,31,30,31,31,30,31,30,31,0 };

Calendar::Calendar() : year(0),month(0),day(0),hour(0),minute(0),second(0),julian(0),greg(true) {}
Calendar::Calendar(long y,long m,double d,bool g) { set(y,m,d,0,0,0,g); }
Calendar::Calendar(long y,long m,double d,double h,double n,double s,bool g) { set(y,m,d,h,n,s,g); }
Calendar::Calendar(double jd,bool g) { set(jd,g); }

double Calendar::set(long y,long m,double d,bool g) {
	if(m<3) y = y-1,m = m+12;
	long a = 0,b = 0;
	if(g) a = INT(y/100.0),b = 2-a+INT(a/4.0);
	return INT(365.25*(y+4716))+INT(30.6001*(m+1))+d+b-1524.5;
}

bool Calendar::isLeap(long y,bool g) { return g? ((y%100)==0? ((y%400)==0) : ((y%4)==0)) : ((y%4)==0); }

void Calendar::set(long y,long m,double d,double h,double n,double s,bool g) {
	year = y;
	day = (int)d;
	hour = (int)h;
	minute = (int)n;
	second = s;
	julian = set(y,m,d+(h/24)+(n/1440)+(s/86400),g);
	greg = g;
}

void Calendar::set(double jd,bool g) {
	julian = jd;
	greg = g;
	jd += 0.5;
	double tempZ;
	double F = modf(jd,&tempZ);
	long Z = static_cast<long>(tempZ),A = Z;
	if(greg) {
		long alpha = INT((Z-1867216.25)/36524.25);
		A = Z+1+alpha-INT(alpha/4.0);
	}
	long B = A+1524,C = INT((B-122.1)/365.25),D = INT(365.25*C),E = INT((B-D)/30.6001);
	double dblDay = B-D-INT(30.6001*E)+F;
	day = static_cast<long>(dblDay);
	month = E<14? E-1 : E-13;
	year = month>2? C-4716 : C-4715;
	F = modf(dblDay,&tempZ);
	hour = INT(F*24);
	minute = INT((F-(hour)/24.0)*1440.0);
	second = (F-(hour/24.0)-(minute/1440.0))*86400.0;
}

void Calendar::get(long& y,long& m,long& d,long& h,long& n,double& s) const {
	y = year;
	m = month;
	d = day;
	h = hour;
	n = minute;
	s = second;
}

Calendar::DAY_OF_WEEK Calendar::dayOfWeek() const {
	return static_cast<DAY_OF_WEEK>((static_cast<long>(julian+1.5)%7));
}

long Calendar::daysInMonth() const {
	return month==2 && isLeap(year,greg)? monthLength[1]+1 : monthLength[month-1];
}

long Calendar::daysInYear() const { return isLeap(year,greg)? 366 : 365; }

double Calendar::dayOfYear() const { return julian-set(year,1,1,greg)+1; }

double Calendar::fractionalYear() const {
	long d = daysInYear();
	return year+((julian-set(year,1,1,greg))/d);
}

void Calendar::dayOfYearToDayAndMonth(long DayOfYear,bool bLeap,long& DayOfMonth,long& Month) {
	long K = bLeap ? 1 : 2;
	Month = INT(9*(K+DayOfYear)/275.0+0.98);
	if(DayOfYear<32) Month = 1;
	DayOfMonth = DayOfYear-INT((275*Month)/9.0)+(K*INT((Month+9)/12.0))+30;
}

long Calendar::INT(double value) {
	if(value>=0) return static_cast<long>(value);
	else return static_cast<long>(value-1);
}

CalendarDate Calendar::julianToGregorian(long Year,long Month,long Day) {
	Calendar julian(Year,Month,Day,false);
	long J = static_cast<long>(julian.dayOfYear());
	long JD = Calendar::INT(365.25*(Year-1))+1721423+J;
	long alpha = Calendar::INT((JD-1867216.25)/36524.25);
	long beta = JD+1+alpha-Calendar::INT(alpha/4.0);
	if(JD<2299161) beta = JD;
	long b = beta+1524;
	long c = Calendar::INT((b-122.1)/365.25);
	long d = Calendar::INT(365.25*c);
	long e = Calendar::INT((b-d)/30.6001);
	CalendarDate gregorianDate;
	gregorianDate.day = b-d-INT(30.6001*e);
	if(e<14) gregorianDate.month = e-1;
	else gregorianDate.month = e-13;
	if(gregorianDate.month>2) gregorianDate.year = c-4716;
	else gregorianDate.year = c-4715;
	return gregorianDate;
}

CalendarDate Calendar::gregorianToJulian(long Year,long Month,long Day) {
	if(Month<3) {
		Year--;
		Month += 12;
	}
	long alpha = Calendar::INT(Year/100.0);
	long beta = 2-alpha+Calendar::INT(alpha/4.0);
	long b = Calendar::INT(365.25*Year)+Calendar::INT(30.6001*(Month+1))+Day+1722519+beta;
	long c = Calendar::INT((b-122.1)/365.25);
	long d = Calendar::INT(365.25*c);
	long e = Calendar::INT((b-d)/30.6001);
	CalendarDate julianDate;
	julianDate.day = b-d-INT(30.6001*e);
	if(e<14) julianDate.month = e-1;
	else julianDate.month = e-13;
	if(julianDate.month>2) julianDate.year = c-4716;
	else julianDate.year = c-4715;
	return julianDate;
}

